/* Soot - a J*va Optimization Framework
 * Copyright (C) 2006 Nomair A. Naeem
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

package soot.dava.toolkits.base.AST.transformations;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import soot.BooleanType;
import soot.Value;
import soot.dava.internal.AST.ASTCondition;
import soot.dava.internal.AST.ASTControlFlowNode;
import soot.dava.internal.AST.ASTDoWhileNode;
import soot.dava.internal.AST.ASTForLoopNode;
import soot.dava.internal.AST.ASTIfElseNode;
import soot.dava.internal.AST.ASTIfNode;
import soot.dava.internal.AST.ASTLabeledBlockNode;
import soot.dava.internal.AST.ASTLabeledNode;
import soot.dava.internal.AST.ASTMethodNode;
import soot.dava.internal.AST.ASTNode;
import soot.dava.internal.AST.ASTStatementSequenceNode;
import soot.dava.internal.AST.ASTSwitchNode;
import soot.dava.internal.AST.ASTTryNode;
import soot.dava.internal.AST.ASTUnaryCondition;
import soot.dava.internal.AST.ASTWhileNode;
import soot.dava.internal.javaRep.DIntConstant;
import soot.dava.internal.javaRep.DNotExpr;
import soot.dava.toolkits.base.AST.analysis.DepthFirstAdapter;
import soot.dava.toolkits.base.AST.traversals.ASTParentNodeFinder;

/*
 * if (true)   ---> remove conditional copy ifbody to parent
 * 
 * if(false) eliminate in all entirety
 * 
 * if(true)
 *    bla1
 * else 
 *    bla2        remove conditional copy bla1 to parent
 *    
 * if(false)
 *    bla1
 *  else
 *    bla2      remoce conditional copy bla2 to parent
 *    
 *       
 * while(false)  eliminate in entirety... notice this is not an Uncondition loop but a ASTWhileNode
 * 
 * do{ .... } while(false)  eliminate loop copy body to parent
 * 
 * for(int i =0;false;i++)   remove for .  copy init stmts to parent
 */
public class EliminateConditions extends DepthFirstAdapter {

	public static boolean DEBUG = false;
	public boolean modified = false;

	ASTParentNodeFinder finder;
	ASTMethodNode AST;
	List<Object> bodyContainingNode = null;

	public EliminateConditions(ASTMethodNode AST) {
		super();
		finder = new ASTParentNodeFinder();
		this.AST = AST;
	}

	public EliminateConditions(boolean verbose, ASTMethodNode AST) {
		super(verbose);
		finder = new ASTParentNodeFinder();
		this.AST = AST;
	}

	public void normalRetrieving(ASTNode node) {
		modified = false;
		if (node instanceof ASTSwitchNode) {
			do {
				modified = false;
				dealWithSwitchNode((ASTSwitchNode) node);
			} while (modified);
			return;
		}
		// from the Node get the subBodes
		Iterator<Object> sbit = node.get_SubBodies().iterator();
		while (sbit.hasNext()) {
			List subBody = (List) sbit.next();
			Iterator it = subBody.iterator();
			ASTNode temp = null;
			Boolean returned = null;
			while (it.hasNext()) {
				temp = (ASTNode) it.next();

				// only check condition if this is a control flow node
				if (temp instanceof ASTControlFlowNode) {
					bodyContainingNode = null;
					returned = eliminate(temp);
					if (returned != null && canChange(returned, temp)) {
						break;
					} else {
						if (DEBUG)
							System.out.println("returned is null"
									+ temp.getClass());
						bodyContainingNode = null;
					}
				}
				temp.apply(this);
			}// end while going through nodes in subBody

			boolean changed = change(returned, temp);
			if (changed)
				modified = true;
		}// end of going over subBodies

		if (modified) {
			// repeat the whole thing
			normalRetrieving(node);
		}
	}

	public Boolean eliminate(ASTNode node) {
		ASTCondition cond = null;
		if (node instanceof ASTControlFlowNode)
			cond = ((ASTControlFlowNode) node).get_Condition();
		else
			return null;

		if (cond == null || !(cond instanceof ASTUnaryCondition))
			return null;

		ASTUnaryCondition unary = (ASTUnaryCondition) cond;
		Value unaryValue = unary.getValue();

		boolean notted = false;
		if (unaryValue instanceof DNotExpr) {
			notted = true;
			unaryValue = ((DNotExpr) unaryValue).getOp();
		}

		Boolean isBoolean = isBooleanConstant(unaryValue);
		if (isBoolean == null) {
			// not a constant
			return null;
		}

		boolean trueOrFalse = isBoolean.booleanValue();
		if (notted) {
			// since it is notted we reverse the booleans
			trueOrFalse = !trueOrFalse;
		}

		AST.apply(finder);

		Object temp = finder.getParentOf(node);
		if (temp == null)
			return null;

		ASTNode parent = (ASTNode) temp;
		List<Object> subBodies = parent.get_SubBodies();
		Iterator<Object> it = subBodies.iterator();

		int index = -1;
		while (it.hasNext()) {
			bodyContainingNode = (List<Object>) it.next();
			index = bodyContainingNode.indexOf(node);
			if (index < 0) {
				bodyContainingNode = null;
			} else {
				// bound the body containing Node
				return new Boolean(trueOrFalse);
			}
		}
		return null;

	}

	/*
	 * Method returns null if the Value is not a constant or not a boolean
	 * constant return true if the constant is true return false if the constant
	 * is false
	 */
	public Boolean isBooleanConstant(Value internal) {

		if (!(internal instanceof DIntConstant))
			return null;

		if (DEBUG)
			System.out.println("Found Constant");

		DIntConstant intConst = (DIntConstant) internal;

		if (!(intConst.type instanceof BooleanType))
			return null;

		// either true or false
		if (DEBUG)
			System.out.println("Found Boolean Constant");

		if (intConst.value == 1) {
			return new Boolean(true);
		} else if (intConst.value == 0) {
			return new Boolean(false);
		} else
			throw new RuntimeException(
					"BooleanType found with value different than 0 or 1");
	}

	public Boolean eliminateForTry(ASTNode node) {
		ASTCondition cond = null;
		if (node instanceof ASTControlFlowNode)
			cond = ((ASTControlFlowNode) node).get_Condition();
		else
			return null;

		if (cond == null || !(cond instanceof ASTUnaryCondition))
			return null;

		ASTUnaryCondition unary = (ASTUnaryCondition) cond;
		Value unaryValue = unary.getValue();

		boolean notted = false;
		if (unaryValue instanceof DNotExpr) {
			notted = true;
			unaryValue = ((DNotExpr) unaryValue).getOp();
		}

		Boolean isBoolean = isBooleanConstant(unaryValue);
		if (isBoolean == null) {
			// not a constant
			return null;
		}

		boolean trueOrFalse = isBoolean.booleanValue();
		if (notted) {
			// since it is notted we reverse the booleans
			trueOrFalse = !trueOrFalse;
		}

		AST.apply(finder);

		Object temp = finder.getParentOf(node);
		if (temp == null)
			return null;

		if (!(temp instanceof ASTTryNode))
			throw new RuntimeException(
					"eliminateTry called when parent was not a try node");

		ASTTryNode parent = (ASTTryNode) temp;

		List<Object> tryBody = parent.get_TryBody();

		int index = tryBody.indexOf(node);
		if (index >= 0) {
			// bound the body containing Node
			bodyContainingNode = tryBody;
			return new Boolean(trueOrFalse);
		}

		List<Object> catchList = parent.get_CatchList();
		Iterator<Object> it = catchList.iterator();
		while (it.hasNext()) {
			ASTTryNode.container catchBody = (ASTTryNode.container) it.next();

			List<Object> body = (List<Object>) catchBody.o;
			index = body.indexOf(node);
			if (index >= 0) {
				// bound the body containing Node
				bodyContainingNode = body;
				return new Boolean(trueOrFalse);
			}

		}
		return null;
	}

	public void caseASTTryNode(ASTTryNode node) {
		modified = false;
		inASTTryNode(node);
		// get try body iterator
		Iterator<Object> it = node.get_TryBody().iterator();

		Boolean returned = null;
		ASTNode temp = null;
		while (it.hasNext()) {
			temp = (ASTNode) it.next();
			// only check condition if this is a control flow node
			if (temp instanceof ASTControlFlowNode) {
				bodyContainingNode = null;
				returned = eliminateForTry(temp);
				if (returned != null && canChange(returned, temp))
					break;
				else
					bodyContainingNode = null;
			}
			temp.apply(this);
		}// end while

		boolean changed = change(returned, temp);
		if (changed)
			modified = true;

		// get catch list and apply on the following
		// a, type of exception caught ......... NO NEED
		// b, local of exception ............... NO NEED
		// c, catchBody
		List<Object> catchList = node.get_CatchList();
		Iterator itBody = null;
		it = catchList.iterator();
		while (it.hasNext()) {
			ASTTryNode.container catchBody = (ASTTryNode.container) it.next();
			List body = (List) catchBody.o;
			itBody = body.iterator();

			returned = null;
			temp = null;
			// go over the ASTNodes and apply
			while (itBody.hasNext()) {
				temp = (ASTNode) itBody.next();
				// System.out.println("Next node is "+temp);
				// only check condition if this is a control flow node
				if (temp instanceof ASTControlFlowNode) {
					bodyContainingNode = null;
					returned = eliminateForTry(temp);
					if (returned != null && canChange(returned, temp))
						break;
					else
						bodyContainingNode = null;
				}
				temp.apply(this);
			}
			changed = change(returned, temp);
			if (changed)
				modified = true;
		}
		outASTTryNode(node);
		if (modified) {
			// repeat the whole thing
			caseASTTryNode(node);
		}
	}

	public boolean canChange(Boolean returned, ASTNode temp) {
		return true;
	}

	public boolean change(Boolean returned, ASTNode temp) {
		if (bodyContainingNode != null && returned != null && temp != null) {

			int index = bodyContainingNode.indexOf(temp);
			if (DEBUG)
				System.out.println("in change");
			if (temp instanceof ASTIfNode) {
				bodyContainingNode.remove(temp);

				if (returned.booleanValue()) {
					// if statement and value was true put the body of if into
					// the code
					// if its a labeled stmt we need a labeled block instead
					// notice that its okkay to put a labeled block since other
					// transformations might remove it
					String label = ((ASTLabeledNode) temp).get_Label()
							.toString();
					if (label != null) {
						ASTLabeledBlockNode labeledNode = new ASTLabeledBlockNode(
								((ASTLabeledNode) temp).get_Label(),
								(List<Object>) temp.get_SubBodies().get(0));
						bodyContainingNode.add(index, labeledNode);
					} else {
						bodyContainingNode.addAll(index, (List) temp
								.get_SubBodies().get(0));
					}
				}
				if (DEBUG)
					System.out.println("Removed if" + temp);
				return true;
			} else if (temp instanceof ASTIfElseNode) {
				bodyContainingNode.remove(temp);
				if (returned.booleanValue()) {
					// true so the if branch's body has to be added
					// if its a labeled stmt we need a labeled block instead
					// notice that its okkay to put a labeled block since other
					// transformations might remove it
					String label = ((ASTLabeledNode) temp).get_Label()
							.toString();
					if (label != null) {
						ASTLabeledBlockNode labeledNode = new ASTLabeledBlockNode(
								((ASTLabeledNode) temp).get_Label(),
								(List<Object>) temp.get_SubBodies().get(0));
						bodyContainingNode.add(index, labeledNode);
					} else {
						bodyContainingNode.addAll(index, (List) temp
								.get_SubBodies().get(0));
					}
				} else {
					// if its a labeled stmt we need a labeled block instead
					// notice that its okkay to put a labeled block since other
					// transformations might remove it
					String label = ((ASTLabeledNode) temp).get_Label()
							.toString();
					if (label != null) {
						ASTLabeledBlockNode labeledNode = new ASTLabeledBlockNode(
								((ASTLabeledNode) temp).get_Label(),
								(List<Object>) temp.get_SubBodies().get(1));
						bodyContainingNode.add(index, labeledNode);
					} else {
						bodyContainingNode.addAll(index, (List) temp
								.get_SubBodies().get(1));
					}
				}
				return true;
			} else if (temp instanceof ASTWhileNode
					&& returned.booleanValue() == false) {
				// notice we only remove if ASTWhileNode has false condition
				bodyContainingNode.remove(temp);
				return true;
			} else if (temp instanceof ASTDoWhileNode
					&& returned.booleanValue() == false) {
				// System.out.println("in try dowhile false");
				// remove the loop copy the body out since it gets executed once
				bodyContainingNode.remove(temp);
				bodyContainingNode.addAll(index, (List) temp.get_SubBodies()
						.get(0));
				return true;
			} else if (temp instanceof ASTForLoopNode
					&& returned.booleanValue() == false) {
				bodyContainingNode.remove(temp);
				ASTStatementSequenceNode newNode = new ASTStatementSequenceNode(
						((ASTForLoopNode) temp).getInit());
				bodyContainingNode.add(index, newNode);
				return true;
			}
		}
		return false;
	}

	public void dealWithSwitchNode(ASTSwitchNode node) {
		List<Object> indexList = node.getIndexList();
		Map<Object, List<Object>> index2BodyList = node.getIndex2BodyList();

		Iterator<Object> it = indexList.iterator();
		while (it.hasNext()) {
			// going through all the cases of the switch statement
			Object currentIndex = it.next();
			List body = index2BodyList.get(currentIndex);

			if (body != null) {
				// this body is a list of ASTNodes

				Iterator itBody = body.iterator();
				Boolean returned = null;
				ASTNode temp = null;
				while (itBody.hasNext()) {
					temp = (ASTNode) itBody.next();
					if (temp instanceof ASTControlFlowNode) {
						bodyContainingNode = null;
						returned = eliminate(temp);
						if (returned != null && canChange(returned, temp))
							break;
						else
							bodyContainingNode = null;
					}
					temp.apply(this);
				}
				boolean changed = change(returned, temp);
				if (changed)
					modified = true;
			}// end while changed
		}
	}
}
